package com.txtsqlclient.ws;

import java.io.File;
import java.io.IOException;
import java.text.DateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.apache.commons.fileupload.FileItem;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import Acme.Serve.ServeSessionManager;

import com.txtsqlclient.dataaccess.database.connection.ConnectionException;
import com.txtsqlclient.dataaccess.database.connection.ConnectionFactory;
import com.txtsqlclient.dataaccess.file.DataSourceTypeDAO;
import com.txtsqlclient.domain.DataSource;
import com.txtsqlclient.domain.DataSourceType;
import com.txtsqlclient.domain.Database;
import com.txtsqlclient.domain.FileDataSource;

@SuppressWarnings("serial")
public class LoginServlet extends BaseServlet {

	public static final String DATA_SOURCE_TYPE_PARAM = "dataSourceType";
	public static final String DATABASE_PARAM = "database";
	public static final String DATABASE_TYPE_INVALID_MSG = "Database Type is empty or invalid.";
	public static final String DIR_PATH_PARAM = "dirPath";
	public static final String FILEDATA_PARAM = "Filedata";
	public static final String HOSTNAME_INVALID_MSG = "Database Hostname is empty";
	public static final String FILE_EMPTY_MSG = "File is not uploaded";
	public static final String HOSTNAME_PARAM = "hostname";
	private static Log LOGGER = LogFactory.getLog(LoginServlet.class);
	public static final String PASSWORD_INVALID_MSG = "Password is empty";
	public static final String PASSWORD_PARAM = "password";
	public static final String TEMP_DIR_PATTERN = DataSourceTypeDAO.SC_HOME
			.replace('\\', '/')
			+ "/tmp/{sessionId}";
	public static final String USERNAME_INVALID_MSG = "Username is empty";
	public static final String USERNAME_PARAM = "username";

	protected void populateErrorResponse(final HttpServletRequest req,
			final HttpServletResponse res, final Throwable error,
			final String errorMessage) throws IOException {
		res.setStatus(HttpServletResponse.SC_OK);
		res.setContentType("text/xml");
		final ServletOutputStream out = res.getOutputStream();
		out
				.println("<p:LoginResponse xmlns:p='http://code.google.com/p/txt-sql-client' "
						+ ServeSessionManager.SESSION_COOKIE_NAME
						+ "='"
						+ req.getSession().getId() + "'><Errors>");
		out.println("<Error>" + errorMessage + "</Error>");
		Throwable t = error;
		while (t != null) {
			out.println("<Error>" + t.getMessage() + "</Error>");
			t = t.getCause();
		}
		if (error instanceof ValidationException) {
			for (final String valErr : ((ValidationException) error)
					.getValidationErrors()) {
				out.println("<Error>" + valErr + "</Error>");
			}
		}
		out.println("</Errors></p:LoginResponse>");
		out.flush();
		out.close();
	}

	protected void populateResponse(final HttpServletRequest req,
			final HttpServletResponse res) throws IOException {
		res.setStatus(HttpServletResponse.SC_OK);
		res.setContentType("text/xml");
		final ServletOutputStream out = res.getOutputStream();
		out
				.println("<p:LoginResponse xmlns:p='http://code.google.com/p/txt-sql-client' "
						+ ServeSessionManager.SESSION_COOKIE_NAME
						+ "='"
						+ req.getSession().getId() + "'></p:LoginResponse>");
		out.flush();
		out.close();
	}

	@Override
	protected void processRequest(final HttpServletRequest req,
			final HttpServletResponse res) throws IOException {
		LOGGER.info("Request received for Login at: "
				+ DateFormat.getDateTimeInstance().format(
						Calendar.getInstance().getTime()));
		final HttpSession session = req.getSession();
		try {
			final DataSource ds = validate(req);
			ConnectionFactory.getInstance().getConnection(ds);
			populateResponse(req, res);
			session.setAttribute(QueryServlet.DATA_SOURCE_SESSION_VARIABLE, ds);
		} catch (final ConnectionException e) {
			populateErrorResponse(req, res, e,
					"Error connecting to the database. Please verify the login information.");
			LOGGER.error("Error connecting to the database.", e);
		} catch (final ValidationException ve) {
			populateErrorResponse(req, res, ve,
					"Error validating the login information. Please verify.");
			LOGGER.error("Error validating login information", ve);
		}
	}

	/**
	 * This method is required to populate and validate the Data Source details
	 * from the HTTP request
	 * 
	 * @param req
	 *            The HTTP Request object
	 * @param res
	 *            The HTTP Response object
	 * @return a DataSource object populate with the parameters present in the
	 *         HTTP request
	 * @throws ValidationException
	 *             if any of the required fields are not present in the HTTP
	 *             request
	 */
	protected DataSource validate(final HttpServletRequest req)
			throws ValidationException {
		DataSource ds = null;
		String error = null;
		final String dst = req
				.getParameter(LoginServlet.DATA_SOURCE_TYPE_PARAM);
		final DataSourceType dataSourceType = DataSourceType
				.resolveDataSourceType(dst);
		if (dataSourceType == null) {
			error = LoginServlet.DATABASE_TYPE_INVALID_MSG;
			final ValidationException ve = new ValidationException(
					"Some required fields have not been filled.");
			ve.addValidationError(error);
			throw ve;
		}
		if (dataSourceType.isDatabase()) {
			ds = validateDatabaseDetails(req, dataSourceType);
		} else {
			ds = validateFileDetails(req, dataSourceType);
		}
		return ds;
	}

	/**
	 * This method is required to read and validate the database details from
	 * the HTTP request.
	 * 
	 * @param req
	 *            The HTTP Request object
	 * @param res
	 *            The HTTP Response object
	 * @param dataSourceType
	 *            The Data Source Type selected by the user
	 * @return a Database object populated with the parameters present in the
	 *         HTTP request
	 * @throws ValidationException
	 *             if any of the required fields are not present in the HTTP
	 *             request
	 */
	protected Database validateDatabaseDetails(final HttpServletRequest req,
			final DataSourceType dataSourceType) throws ValidationException {
		final List<String> errors = new ArrayList<String>();
		final String hostname = req.getParameter(LoginServlet.HOSTNAME_PARAM);
		final String database = req.getParameter(LoginServlet.DATABASE_PARAM);
		final String username = req.getParameter(LoginServlet.USERNAME_PARAM);
		final String password = req.getParameter(LoginServlet.PASSWORD_PARAM);
		if (hostname == null || hostname.trim().length() == 0) {
			errors.add(LoginServlet.HOSTNAME_INVALID_MSG);
		}
		if (username == null || username.trim().length() == 0) {
			errors.add(LoginServlet.USERNAME_INVALID_MSG);
		}
		if (password == null || password.trim().length() == 0) {
			errors.add(LoginServlet.PASSWORD_INVALID_MSG);
		}
		if (!errors.isEmpty()) {
			final ValidationException ve = new ValidationException(
					"Some required fields have not been filled.");
			ve.addValidationErrors(errors);
			throw ve;
		}
		final Database db = new Database(dataSourceType, hostname, database,
				username, password);
		return db;
	}

	/**
	 * This method is required to read and validate the file details from the
	 * user input.
	 * 
	 * @param req
	 *            The HTTP Request object
	 * @param res
	 *            The HTTP Response object
	 * @param dataSourceType
	 *            The Data Source Type selected by the user
	 * @return a FileDataSource object populated with the parameters present in
	 *         the HTTP request
	 * @throws ValidationException
	 *             if any of the required fields are not present in the HTTP
	 *             request
	 */
	protected FileDataSource validateFileDetails(final HttpServletRequest req,
			final DataSourceType dataSourceType) throws ValidationException {
		final FileItem item = (FileItem) req.getSession().getAttribute(
				LoginServlet.FILEDATA_PARAM);
		if (item == null) {
			ValidationException ex = new ValidationException(
					"Some required fields are not present.");
			ex.addValidationError(LoginServlet.FILE_EMPTY_MSG);
			throw ex;
		}
		final String dirPath = TEMP_DIR_PATTERN.replace("{sessionId}", req
				.getSession().getId());
		final String fileName = item.getName();
		final File tempFileOnServer = new File(dirPath + "/" + fileName);
		if (!tempFileOnServer.exists()) {
			tempFileOnServer.getParentFile().mkdirs();
		}
		try {
			item.write(tempFileOnServer);
		} catch (final Exception e) {
			throw new ValidationException(
					"Error saving the uploaded file to temporary location", e);
		}
		final FileDataSource fds = new FileDataSource(dataSourceType, dirPath);
		return fds;
	}
}
